/* * The MIT License * * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Bruce Chapman, Yahoo! Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.scm; import com.gargoylesoftware.htmlunit.ElementNotFoundException; import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.html.HtmlAnchor; import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlPage; import hudson.FilePath; import hudson.Proc; import hudson.model.Cause; import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; import hudson.model.Hudson; import hudson.model.Result; import hudson.scm.PollingResult.Change; import hudson.scm.browsers.Sventon; import hudson.scm.subversion.UpdateUpdater; import hudson.util.StreamTaskListener; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.dom4j.Document; import org.dom4j.io.DOMReader; import org.junit.Ignore; import org.jvnet.hudson.test.Bug; import org.jvnet.hudson.test.CaptureEnvironmentBuilder; import org.jvnet.hudson.test.HudsonHomeLoader.CopyExisting; import org.jvnet.hudson.test.recipes.PresetData; import org.tmatesoft.svn.core.SVNCancelException; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.auth.SVNAuthentication; import org.tmatesoft.svn.core.auth.SVNPasswordAuthentication; import org.tmatesoft.svn.core.auth.SVNSSHAuthentication; import org.tmatesoft.svn.core.auth.SVNUserNameAuthentication; import org.tmatesoft.svn.core.wc.SVNClientManager; import org.tmatesoft.svn.core.wc.SVNCommitClient; import static hudson.scm.SubversionSCM.compareSVNAuthentications; import org.springframework.security.context.SecurityContextHolder; import org.tmatesoft.svn.core.auth.*; /** * @author Kohsuke Kawaguchi */ public class SubversionCommonTest extends AbstractSubversionTest { private static final String GUEST_ACCESS_REPOSITORY_RESOURCE = "guest_access_svn.zip"; private static final String realm = "<svn://localhost:3690>"; private static final String SVN_URL = "svn://localhost/bob"; private static final String GUEST_USER_LOGIN = "guest"; private static final String GUEST_USER_PASSWORD = "guestpass"; private static final String BOGUS_USER_LOGIN = "bogus"; private static final String BOGUS_USER_PASSWORD = "boguspass"; private static final int LOG_LIMIT = 1000; protected static final String SVN_URL1 = "http://svn.apache.org/repos/asf/subversion/trunk/doc"; protected static final String SVN_URL2 = "http://svn.apache.org/repos/asf/subversion/trunk/packages"; public void testMatcher() { check("http://foobar/"); check("https://foobar/"); check("file://foobar/"); check("svn://foobar/"); check("svn+ssh://foobar/"); } public void testMacther2() { String[] r = "abc\\ def ghi".split("(?<!\\\\)[ \\r\\n]+"); for (int i = 0; i < r.length; i++) { r[i] = r[i].replaceAll("\\\\ ", " "); } System.out.println(Arrays.asList(r)); assertEquals(r.length, 2); } private void check(String url) { assertTrue(SubversionSCM.URL_PATTERN.matcher(url).matches()); } //TODO Investigate why System user is used instead of anonymous after migration to 2.0.0 version @PresetData(PresetData.DataSet.ANONYMOUS_READONLY) @Bug(2380) public void testTaggingPermission() throws Exception { // create a build FreeStyleProject p = createFreeStyleProject(); //Set anonymous user for authentication. SecurityContextHolder.getContext().setAuthentication(Hudson.ANONYMOUS); p.setScm(loadSvnRepo()); FreeStyleBuild b = p.scheduleBuild2(0, new Cause.UserCause()).get(); System.out.println(b.getLog(LOG_LIMIT)); assertBuildStatus(Result.SUCCESS, b); SubversionTagAction action = b.getAction(SubversionTagAction.class); assertFalse(b.hasPermission(action.getPermission())); WebClient wc = new WebClient(); HtmlPage html = wc.getPage(b); // make sure there's no link to the 'tag this build' Document dom = new DOMReader().read(html); assertNull(dom.selectSingleNode("//A[text()='Tag this build']")); for (HtmlAnchor a : html.getAnchors()) { assertFalse(a.getHrefAttribute().contains("/tagBuild/")); } // and no tag form on tagBuild page html = wc.getPage(b, "tagBuild/"); try { html.getFormByName("tag"); fail("should not have been found"); } catch (ElementNotFoundException e) { } // and that tagging would fail try { wc.getPage(b, "tagBuild/submit?name0=test&Submit=Tag"); fail("should have been denied"); } catch (FailingHttpStatusCodeException e) { // make sure the request is denied assertEquals(e.getResponse().getStatusCode(), 403); } // now login as alice and make sure that the tagging would succeed wc = new WebClient(); wc.login("alice", "alice"); html = wc.getPage(b, "tagBuild/"); HtmlForm form = html.getFormByName("tag"); submit(form); } public void testConfigRoundtrip() throws Exception { FreeStyleProject p = createFreeStyleProject(); SubversionSCM scm = new SubversionSCM( Arrays.asList( new SubversionSCM.ModuleLocation( "https://svn.java.net/svn/hudson~svn/trunk/hudson/test-projects/testSubversionExclusion", "c"), new SubversionSCM.ModuleLocation( "https://svn.java.net/svn/hudson~svn/trunk/hudson/test-projects/testSubversionExclusion", "d")), true, new Sventon(new URL("http://www.sun.com/"), "test"), "exclude", "user", "revprop", "excludeMessage"); p.setScm(scm); submit(new WebClient().getPage(p, "configure").getFormByName("config")); verify(scm, (SubversionSCM) p.getScm()); scm = new SubversionSCM( Arrays.asList( new SubversionSCM.ModuleLocation( "https://svn.java.net/svn/hudson~svn/trunk/hudson/test-projects/testSubversionExclusion", "c")), false, null, "", "", "", ""); p.setScm(scm); submit(new WebClient().getPage(p, "configure").getFormByName("config")); verify(scm, (SubversionSCM) p.getScm()); } @Bug(7944) @Ignore public void testConfigRoundtrip2() throws IOException { FreeStyleProject p = createFreeStyleProject(); SubversionSCM scm = new SubversionSCM( Arrays.asList( new SubversionSCM.ModuleLocation( "https://svn.java.net/svn/hudson~svn/trunk/hudson/test-projects/testSubversionExclusion", "")), true, null, null, null, null, null); p.setScm(scm); // configRoundtrip(p); verify(scm, (SubversionSCM) p.getScm()); } public void testMasterPolling() throws Exception { File repo = new CopyExisting(getClass().getResource("two-revisions.zip")).allocate(); SubversionSCM scm = new SubversionSCM("file:///" + repo.getPath()); SubversionSCM.POLL_FROM_MASTER = true; FreeStyleProject p = createFreeStyleProject(); p.setScm(scm); p.setAssignedLabel(createSlave().getSelfLabel()); assertBuildStatusSuccess(p.scheduleBuild2(2).get()); // initial polling on the master for the code path that doesn't find any change PollingResult pollingResult = p.poll(new StreamTaskListener(System.out, null)); if (pollingResult.getChange() != PollingResult.Change.INCOMPARABLE) { boolean hasChanges = pollingResult.hasChanges(); assertFalse("Polling should not have any changes for an initially created slave", hasChanges); } // create a commit FreeStyleProject forCommit = createFreeStyleProject(); forCommit.setScm(scm); forCommit.setAssignedLabel(hudson.getSelfLabel()); FreeStyleBuild b = assertBuildStatusSuccess(forCommit.scheduleBuild2(0).get()); FilePath newFile = b.getWorkspace().child("foo"); newFile.touch(System.currentTimeMillis()); SVNClientManager svnm = SubversionSCM.createSvnClientManager(p); svnm.getWCClient().doAdd(new File(newFile.getRemote()), false, false, false, SVNDepth.INFINITY, false, false); SVNCommitClient cc = svnm.getCommitClient(); cc.doCommit(new File[]{new File(newFile.getRemote())}, false, "added", false, false); // polling on the master for the code path that doesn't find any change assertTrue(p.poll(new StreamTaskListener(System.out, null)).hasChanges()); } public void testCompareSVNAuthentications() { assertFalse(compareSVNAuthentications(new SVNUserNameAuthentication("me", true, null, false), new SVNSSHAuthentication("me", "me", 22, true, null, false))); // same object should compare equal _idem(new SVNUserNameAuthentication("me", true, null, false)); _idem(new SVNSSHAuthentication("me", "pass", 22, true, null, false)); _idem(new SVNSSHAuthentication("me", new File("./some.key"), null, 23, false, null, false)); _idem(new SVNSSHAuthentication("me", "key".toCharArray(), "phrase", 0, false, null, false)); _idem(new SVNPasswordAuthentication("me", "pass", true, null, false)); _idem(new SVNSSLAuthentication("certificate".getBytes(), null, true)); // make sure two Files and char[]s compare the same assertTrue(compareSVNAuthentications( new SVNSSHAuthentication("me", new File("./some.key"), null, 23, false, null, false), new SVNSSHAuthentication("me", new File("./some.key"), null, 23, false, null, false))); assertTrue(compareSVNAuthentications( new SVNSSHAuthentication("me", "key".toCharArray(), "phrase", 0, false, null, false), new SVNSSHAuthentication("me", "key".toCharArray(), "phrase", 0, false, null, false))); // negative cases assertFalse(compareSVNAuthentications( new SVNSSHAuthentication("me", new File("./some1.key"), null, 23, false, null, false), new SVNSSHAuthentication("me", new File("./some2.key"), null, 23, false, null, false))); assertFalse(compareSVNAuthentications( new SVNSSHAuthentication("me", "key".toCharArray(), "phrase", 0, false, null, false), new SVNSSHAuthentication("yo", "key".toCharArray(), "phrase", 0, false, null, false))); } /** * Make sure that a failed credential doesn't result in an infinite loop */ @Bug(2909) public void ignore_testInfiniteLoop() throws Exception { //Start local svn repository Proc server = runSvnServe(getClass().getResource(GUEST_ACCESS_REPOSITORY_RESOURCE)); if (server != null) { SVNURL repo = SVNURL.parseURIDecoded(SVN_URL); try { // creates a purely in memory auth manager ISVNAuthenticationManager m = createInMemoryManager(); // double check that it really knows nothing about the fake repo try { m.getFirstAuthentication(kind, realm, repo); fail(); } catch (SVNCancelException e) { // yep } SVNPasswordAuthentication authentication = new SVNPasswordAuthentication(BOGUS_USER_LOGIN, BOGUS_USER_PASSWORD, true, null, false); m.acknowledgeAuthentication(false, kind, realm, null, authentication); authentication = new SVNPasswordAuthentication(GUEST_USER_LOGIN, GUEST_USER_PASSWORD, true, null, false); m.acknowledgeAuthentication(true, kind, realm, null, authentication); // emulate the call flow where the credential fails List<SVNAuthentication> attempted = new ArrayList<SVNAuthentication>(); SVNAuthentication a = m.getFirstAuthentication(kind, realm, repo); assertNotNull(a); attempted.add(a); for (int i = 0; i < 10; i++) { m.acknowledgeAuthentication(false, kind, realm, SVNErrorMessage.create(SVNErrorCode.RA_NOT_AUTHORIZED), a); try { a = m.getNextAuthentication(kind, realm, repo); assertNotNull(a); attempted.add(a); } catch (SVNCancelException e) { // make sure we've tried our fake credential for (SVNAuthentication aa : attempted) { if (aa instanceof SVNPasswordAuthentication) { SVNPasswordAuthentication pa = (SVNPasswordAuthentication) aa; if (GUEST_USER_LOGIN.equals(pa.getUserName()) && GUEST_USER_PASSWORD.equals(pa.getPassword())) { return; // yep } } } fail("Hudson didn't try authentication"); } } fail("Looks like we went into an infinite loop"); } finally { server.kill(); } } else { System.out.println("Skipping testSuperUserForAllRepos. Light weight SVN Server (svnserve) could not be started."); } } public void testMultiModuleEnvironmentVariables() throws Exception { FreeStyleProject p = createFreeStyleProject(); SubversionSCM.ModuleLocation[] locations = { new SubversionSCM.ModuleLocation(SVN_URL1, null), new SubversionSCM.ModuleLocation(SVN_URL2, null) }; p.setScm(new SubversionSCM(Arrays.asList(locations), new UpdateUpdater(), null, null, null, null, null, null)); CaptureEnvironmentBuilder builder = new CaptureEnvironmentBuilder(); p.getBuildersList().add(builder); assertBuildStatusSuccess(p.scheduleBuild2(0).get()); assertEquals(SVN_URL1, builder.getEnvVars().get("SVN_URL_1")); assertEquals(SVN_URL2, builder.getEnvVars().get("SVN_URL_2")); assertEquals(getActualRevision(p.getLastBuild(), SVN_URL1).toString(), builder.getEnvVars().get("SVN_REVISION_1")); assertEquals(getActualRevision(p.getLastBuild(), SVN_URL2).toString(), builder.getEnvVars().get("SVN_REVISION_2")); } public void testSingleModuleEnvironmentVariables() throws Exception { FreeStyleProject p = createFreeStyleProject(); p.setScm(new SubversionSCM(SVN_URL1)); CaptureEnvironmentBuilder builder = new CaptureEnvironmentBuilder(); p.getBuildersList().add(builder); assertBuildStatusSuccess(p.scheduleBuild2(0).get()); assertEquals(SVN_URL1, builder.getEnvVars().get("SVN_URL")); assertEquals(getActualRevision(p.getLastBuild(), SVN_URL1).toString(), builder.getEnvVars().get("SVN_REVISION")); } private void verify(SubversionSCM lhs, SubversionSCM rhs) { SubversionSCM.ModuleLocation[] ll = lhs.getLocations(); SubversionSCM.ModuleLocation[] rl = rhs.getLocations(); assertEquals(ll.length, rl.length); for (int i = 0; i < ll.length; i++) { assertEquals(ll[i].local, rl[i].local); assertEquals(ll[i].remote, rl[i].remote); assertEquals(ll[i].getDepthOption(), rl[i].getDepthOption()); assertEquals(ll[i].isIgnoreExternalsOption(), rl[i].isIgnoreExternalsOption()); } assertNullEquals(lhs.getExcludedRegions(), rhs.getExcludedRegions()); assertNullEquals(lhs.getExcludedUsers(), rhs.getExcludedUsers()); assertNullEquals(lhs.getExcludedRevprop(), rhs.getExcludedRevprop()); assertNullEquals(lhs.getExcludedCommitMessages(), rhs.getExcludedCommitMessages()); assertNullEquals(lhs.getIncludedRegions(), rhs.getIncludedRegions()); } private void assertNullEquals(String left, String right) { if (left == null) { left = ""; } if (right == null) { right = ""; } assertEquals(left, right); } /** * Loads a test Subversion repository into a temporary directory, and * creates {@link hudson.scm.SubversionSCM} for it. */ private SubversionSCM loadSvnRepo() throws Exception { return new SubversionSCM( "file://" + new CopyExisting(getClass().getResource("/svn-repo.zip")).allocate().toURI().toURL().getPath() + "trunk/a", "a"); } private Long getActualRevision(FreeStyleBuild b, String url) throws Exception { SVNRevisionState revisionState = b.getAction(SVNRevisionState.class); if (revisionState == null) { throw new Exception("No revision found!"); } return revisionState.revisions.get(url); } private void _idem(SVNAuthentication a) { assertTrue(compareSVNAuthentications(a, a)); } }